/** @file    BuildingManagerBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: BuildingManagerObjects.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the wosh::services::BuildingManagerBundle class.
 * The header for this class can be found in BuildingManagerBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "BuildingManagerBundle.h"

 #include <core/MethodsCommon.h>
 #include <core/Fact.h>
 #include <core/Table.h>
 #include <core/PersistenceManager.h>

 #include <core/SecurityManager.h>
 #include <core/DeviceManager.h>
 #include <core/FileSystem.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>

 #include <framework/building/Room.h>


using namespace std;
namespace wosh {
 namespace services {

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// LOAD/SAVE OBJECTs

WRESULT BuildingManagerBundle::loadBuildingObjects() {
	// may also be a folder of .xml files
	std::string xmlfile = Properties.getValueOf(_BuildingManager_KEY_DB).toString();
	Log(LOG_VERBOSE, ":loadBuildingObjects() File %s ..", xmlfile.c_str() );
	std::vector<BuildingObject*> objects_load;
	WRESULT ret = PersistenceManager::loadObjects( objects_load, "XML", xmlfile );

	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadBuildingObjects", "FAILED#%d loading Database %s..", ret, xmlfile.c_str() );
		return ret;
	 }
	if ( objects_load.size() == 0 ) {
		Log(LOG_WARNING, ":loadBuildingObjects() : No Items found in database.." );
		return WRET_OK;
	 }
	// register objects
	this->objects.transactionBeginWrite();
	std::vector<BuildingObject*>::iterator it_load;
	for ( it_load=objects_load.begin(); it_load!=objects_load.end(); it_load++ ) {
		if ( *it_load == NULL ) continue;
		addObject_( *it_load );
	 }
	Properties.update( _BuildingManager_KEY_count, this->objects.size() );
	Log(LOG_VERBOSE, ":loadBuildingObjects() : Fixing Child-References of %d items..", this->objects.size() );

	tStrBuildingObjectMap::Iterator it;
	const tStrBuildingObjectMap::ConstIterator it_end = this->objects.end();

	// check if child/device are missing, fix inter-references (parent/child)
	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		fixReferences_( it->second );
	 }

	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		applyNested_(it->second);
		if ( !it->second->getParentName().empty() ) continue;
		Variant locationParentData = it->second->getProperties().getValueOf(_BuildingObject_KEY_Location);
		if ( locationParentData.isEmpty() ) 
			it->second->getProperties().set(_BuildingObject_KEY_Location, it->second->getName() );
	 }

	// ensure permissions are set
	Permission nobodyPerm("nobody", "nobody", Permission::Read);
	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		if ( !it->second->getPermission().isEmpty() ) continue;
		it->second->setPermission(nobodyPerm);
	 }

	this->objects.transactionEnd();
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT BuildingManagerBundle::saveBuildingObjects()
 {
	FilePath xmlFile;
	if ( !xmlFile.set( Properties.getValueOf(_BuildingManager_KEY_DB_shadow).toString() ) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveBuildingObjects", "FAILED#1 Invalid Archive File [%s]", xmlFile.getPath().c_str() );
		return WRET_ERR_PARAM;
	 }
	Log(LOG_VERBOSE, ":saveBuildingObjects() File %s ..", xmlFile.getPathLocal().c_str() );
	this->objects.transactionBeginRead();
	WRESULT ret = PersistenceManager::saveObjects( this->objects.getMap(), "XML", xmlFile.getPathLocal() );
	this->objects.transactionEnd();
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveBuildingObjects", "FAILED#%d saving Database %s..", ret, xmlFile.getPathLocal().c_str() );
	 }
	return ret;
 }

/////////////////////////////////////////////////////////////////////////////////////////// LOAD/SAVE OBJECTs
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT BuildingManagerBundle::addObject_( BuildingObject* bobj ) {
	if ( bobj == NULL ) return WRET_ERR_PARAM;
	if ( bobj->getName().size() < 3 ) return WRET_ERR_PARAM;

	bobj->getURI().setKernelLocal();
	bobj->getURI().setParent( this->getURI() );
	bobj->getURI().setName( bobj->getName() );
	bobj->getURI().registerGlobal();

	bobj->init();

	this->objects.set( bobj->getName(), bobj );
	return WRET_OK;
 }

WRESULT BuildingManagerBundle::removeObject_( BuildingObject* bobj ) {
	if ( bobj == NULL ) return WRET_ERR_PARAM;

	if ( bobj->getURI().isGlobal() )
		bobj->getURI().unRegisterGlobal();

	this->objects.erase(bobj->getName(), true);
	return WRET_OK;
 }

WRESULT BuildingManagerBundle::fixReferencesBy( const string& deviceName )
 {
	InterfacesProvider devInterfaces;
	WRESULT dev_found = WoshKernel::getInstance()->devices().getInterfacesOf(deviceName, &devInterfaces);
	if ( WFAILED(dev_found) ) {
		Log(LOG_WARNING, ":fixReferencesBy(%s) : Cant find/get-interfaces of dev", deviceName.c_str() );
		return WRET_ERR_PARAM;
	 }

	Log(LOG_VERBOSE, ":fixReferencesBy(%s)", deviceName.c_str() );
	int found = 0;
	tStrBuildingObjectMap::Iterator it;
	const tStrBuildingObjectMap::ConstIterator it_end = this->objects.end();
	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		it->second->getChildren().transactionBeginWrite();
		if ( it->second->getChildren().exists(deviceName) ) {
			InterfacesProvider* ip = it->second->getChildren().find(deviceName);
			if ( ip == NULL ) {
				ip = new InterfacesProvider();
				it->second->getChildren().set(deviceName, ip);
			  }
			*ip = devInterfaces;
			// force object to update its state
			it->second->updatedChildren_( deviceName, SecurityManager::generateToken(this) );
			++found;
		 }
		it->second->getChildren().transactionEnd();
	 }
	if ( found > 0 )
		Log(LOG_VERBOSE, ":fixReferencesBy(%s) Updated %d children", deviceName.c_str(), found );

	return WRET_OK;
 }


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT BuildingManagerBundle::fixReferences_( BuildingObject* bobj )
 {
	if ( bobj == NULL ) return WRET_ERR_PARAM;

	if ( bobj->getProperties().exists( _Room_KEY_AccessTo ) ) {
		Variant accessToData = bobj->getProperties().getValueOf( _Room_KEY_AccessTo );
		URI accessToUri( accessToData.toString() );
		bobj->getProperties().set( _Room_KEY_AccessTo, accessToUri.toString() );
	 }

	URI localURI;
	localURI.setKernelLocal();
	localURI.setParent( this->getURI() );
	int warnings = 0;
	bobj->getChildren().transactionBeginWrite();
	// not optimized but browse all children updating even device-child
	tStrBuildingObjectMap::Iterator it_obj;
	BuildingObject::tURIInterfacesProviderMap::Iterator it_child;
	for ( it_child=bobj->getChildren().begin(); it_child!=bobj->getChildren().end(); it_child++ ) {
		if ( it_child->first.isEmpty() || !it_child->first.isValid() ) {
			Log(LOG_WARNING, ":fixReferences_(%s) : Empty/Invalid URI!", bobj->getName().c_str() );
			continue;
		 }
		BuildingObject* refObj = NULL;
		for ( it_obj=this->objects.begin(); it_obj!=this->objects.end(); ++it_obj ) {
			if ( it_obj->second == NULL ) continue;
			if ( it_obj->second->getURI() == it_child->first ) {
				refObj = it_obj->second;
				break;
			 }
		 }

		if ( refObj != NULL ) { // found!
			Log(LOG_VERBOSE, ":fixReferences_(%s) : Child found %s", bobj->getName().c_str(), it_child->first.toString().c_str() );
			if ( it_child->second != NULL )
				delete it_child->second;
			it_child->second = refObj->getInterfaces().clone();
			refObj->setParentBObj(bobj->getURI());
			bobj->updatedChildren_( it_child->first, SecurityManager::generateToken(this) );
		 }
		else { // probably it's a device
			if ( it_child->second == NULL )
				it_child->second = new InterfacesProvider();
			WRESULT dev_found = WoshKernel::getInstance()->devices().getInterfacesOf(it_child->first.getName(), it_child->second);
			if ( WFAILED(dev_found) ) {
				Log(LOG_WARNING, ":fixReferences_(%s) : Cant find/get Dev %s", bobj->getName().c_str(), it_child->first.toString().c_str() );
			 }
			else
				bobj->updatedChildren_( it_child->first, SecurityManager::generateToken(this) );
		 }
	 }

	if ( warnings == 0 )
		Log(LOG_VERBOSE, ":fixReferences_(%s) : All references found [%d]", bobj->getName().c_str(), bobj->getChildren().size() );
	else
		Log(LOG_WARNING, ":fixReferences_(%s) : %d references found, %d warnings", bobj->getName().c_str(), bobj->getChildren().size(), warnings );

	bobj->getChildren().transactionEnd();
	return WRET_OK;
 }

void BuildingManagerBundle::applyNested_( BuildingObject* bobj ) {
	if ( bobj == NULL ) return;
	if ( bobj->getPermission().isEmpty() ) return;
	//bobj->getChildren().transactionBeginRead();

	Variant locationParentData = bobj->getProperties().getValueOf(_BuildingObject_KEY_Location);

	tStrBuildingObjectMap::Iterator it_obj;
	BuildingObject::tURIInterfacesProviderMap::Iterator it_child;
	for ( it_child=bobj->getChildren().begin(); it_child!=bobj->getChildren().end(); it_child++ ) {
		// find the pointer of the child
		BuildingObject* refObj = NULL;
		for ( it_obj=this->objects.begin(); it_obj!=this->objects.end(); it_obj++ ) {
			if ( it_obj->second == NULL ) continue;
			if ( it_obj->second->getURI() == it_child->first ) {
				refObj = it_obj->second;
				break;
			 }
		 }
		if ( refObj == NULL ) continue;

		// fix permissions
		if ( !bobj->getPermission().isEmpty() && refObj->getPermission().isEmpty() ) {
			refObj->setPermission(bobj->getPermission());
		 }
		// fix location
		Variant locationData = refObj->getProperties().getValueOf(_BuildingObject_KEY_Location);
		if ( !locationParentData.isEmpty() && locationData.isEmpty() )  {
			locationData = locationParentData.toString() + "/" + refObj->getName();
			refObj->getProperties().set(_BuildingObject_KEY_Location, locationData);
		 }
		// fix global geometry info
//		if ( !positionParentData.isEmpty() ) {
//		 }

/*
		///@todo set GlobalPosition! should be 3D
		Data positionParentData = object->getProperties().getPropertyValue(_BuildingObject_KEY_Position);
		Data positionData = refObj->getProperties().getPropertyValue(_BuildingObject_KEY_Position);
		if ( !positionParent.isEmpty() && !positionData.isEmpty() ) {

		 }
*/
		// do recusive call
		applyNested_( refObj );
	 }

	//bobj->getChildren().transactionEnd();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* BuildingManagerBundle::mmDoGetTree( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant vRootName;
	Variant vTreeMode;
	request->extractArguments(vTreeMode);
	Log(LOG_VERBOSE, ":mmDoGetTypesTree(%s,%d)", vRootName.toString().c_str(), vTreeMode.toBoolean(true) );
	Tree* objTree = buildTree( vRootName.toString(), vTreeMode.toBoolean(true) );
	return request->replyResponse(WRET_OK, objTree);
 }

Tree* BuildingManagerBundle::buildTree( const std::string& parent, bool children_tree ) const {
	Tree* objTree = new Tree();
	if ( parent.empty() )
		objTree->setTreeName("All BuildingObjects" );
	else
		objTree->setTreeName("Children of " + parent);
	objTree->setRoot( new TreeNode(NULL) );
	this->objects.transactionBeginRead();
	tStrBuildingObjectMap::ConstIterator it;
	const tStrBuildingObjectMap::ConstIterator it_end = this->objects.end();
	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		if ( parent.empty() && it->second->getParentName() != parent ) continue;
		if ( children_tree )
			mergeTreeNodeParent_(it->second, objTree->getRoot());
		else
			mergeTreeNodeParent_(it->second, objTree->getRoot());
		if ( !parent.empty() ) break;
	 }
	this->objects.transactionEnd();
	return objTree;
 }

void BuildingManagerBundle::mergeTreeNodeParent_( const BuildingObject* objNode, TreeNode* parentNode ) const {
	if ( objNode == NULL ) return;
	TreeNode* cNode = new TreeNode( parentNode, new BuildingObject(*objNode) );
	// recursive for childs
	tStrBuildingObjectMap::ConstIterator it;
	const tStrBuildingObjectMap::ConstIterator it_end = this->objects.end();
	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL || it->second == objNode || it->first == objNode->getName() ) continue;
		if ( it->second->getParentName() != objNode->getName() ) continue;
		mergeTreeNodeParent_(it->second, cNode);
	 }
 }

void BuildingManagerBundle::mergeTreeNodeChild_( const BuildingObject* objNode, TreeNode* parentNode ) const {
	if ( objNode == NULL ) return;
	TreeNode* cNode = new TreeNode( parentNode, new BuildingObject(*objNode) );
	// recursive for childs
	tStrBuildingObjectMap::ConstIterator it;
	const tStrBuildingObjectMap::ConstIterator it_end = this->objects.end();
	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL || it->second == objNode || it->first == objNode->getName() ) continue;
		if ( it->second->getName() == objNode->getParentName() ) continue;
		mergeTreeNodeChild_(it->second, cNode);
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::services::BuildingManager_ObjectUpdated, "wosh::Fact", 1.0, _static_BuildingManager_ObjectUpdated )

BuildingManager_ObjectUpdated::BuildingManager_ObjectUpdated( const std::string& obj_name )
	: wosh::Fact( _BuildingManager_EVENT_ObjectUpdated ) {
	setData( new Variant( obj_name ) );
 }

BuildingManager_ObjectUpdated::BuildingManager_ObjectUpdated( const std::string& obj_name, short obj_action )
	: wosh::Fact( _BuildingManager_EVENT_ObjectUpdated ) {
	List* args = List::createFrom<Variant>( obj_name, (int)obj_action );
	setData(args);
 }

std::string BuildingManager_ObjectUpdated::getObjectName() const {
	const Variant* var = getDataItem<Variant>(0);
	if ( var == NULL ) return "";
	if ( var->isString() ) return var->asString();
	return var->toString();
 }

short BuildingManager_ObjectUpdated::getObjectAction() const {
	const Variant* var = getDataItem<Variant>(1);
	if ( var == NULL ) return 1;
	if ( var->isNumeric() ) return 1;
	return var->toInteger(1);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
